package de.logicline.cvss.ui.popup.action;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.ICVSRemoteResource;
import org.eclipse.team.internal.ccvs.core.ICVSResource;
import org.eclipse.team.internal.ccvs.core.ILogEntry;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.Util;
import org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction;
import org.eclipse.team.internal.ccvs.ui.operations.RemoteLogOperation;
import org.eclipse.team.internal.ccvs.ui.operations.RemoteLogOperation.LogEntryCache;

import de.logicline.cvss.IConstants;
import de.logicline.cvss.ui.dialog.SearchDialog;
import de.logicline.cvss.ui.nls.Messages;
import de.logicline.cvss.ui.view.SearchResultView;
import de.logicline.cvss.ui.view.table.TableFilter;
import de.logicline.cvss.vo.CVSSearchEntry;

public class SearchAction extends WorkspaceAction {
	java.util.List changeLogEntriesT; //used by job
	


	
	/*
	 * @see IActionDelegate#run(IAction)
	 */
	public void execute(IAction action) throws InterruptedException,
			InvocationTargetException {

		
		Job job = new SearchAction.BackgroundJob("CVSSearch");
		job.schedule();

		Dialog dialog = new SearchDialog(this.getShell());
		int status = dialog.open();

		if (status == 0) {
			System.out.println(job.getState()); // JOB.Running 
			System.out.println(job.getResult());// != null
			job.join(); 
			System.out.println(job.getState()); // JOB.Running
			System.out.println(job.getResult());// != null			
			TableFilter filter = ((SearchDialog) dialog).getFilter();
			SearchResultView view = (SearchResultView) showView("CVSSearchEngine.view");
			
			ViewerFilter[] filters = view.getViewer().getFilters();
			for (int i = 0; i < filters.length; i++) {
				view.getViewer().removeFilter(filters[i]);
			}
			
			view.getViewer().addFilter(filter);
			
			view.showSearchResults(changeLogEntriesT);
		}
		// run(new IRunnableWithProgress() {
		// public void run(IProgressMonitor monitor) throws
		// InvocationTargetException {
		// IResource[] resources = getSelectedResources();
		// if (resources.length != 1) return;
		// GenericHistoryView view =
		// (GenericHistoryView)showView(GenericHistoryView.VIEW_ID);
		// if (view != null) {
		// }
		// }
		// }, false /* cancelable */, PROGRESS_BUSYCURSOR);
	}

	/**
	 * @see org.eclipse.team.internal.ccvs.ui.actions.CVSAction#getErrorTitle()
	 */
	protected String getErrorTitle() {
		return Messages.search_action_error;
	}

	/**
	 * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForMultipleResources()
	 */
	protected boolean isEnabledForMultipleResources() {
		return true;
	}

	/**
	 * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForAddedResources()
	 */
	protected boolean isEnabledForAddedResources() {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForNonExistantResources()
	 */
	protected boolean isEnabledForNonExistantResources() {
		return true;
	}

	/**
	 * @see org.eclipse.team.internal.ccvs.ui.actions.WorkspaceAction#isEnabledForCVSResource(org.eclipse.team.internal.ccvs.core.ICVSResource)
	 */
	protected boolean isEnabledForCVSResource(ICVSResource cvsResource)
			throws CVSException {
		return true;
		// return (!cvsResource.isFolder() &&
		// super.isEnabledForCVSResource(cvsResource));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.team.internal.ccvs.ui.actions.CVSAction#getId()
	 */
	public String getId() {
		return IConstants.CMD_CVS_SEARCH;
	}

	protected boolean isEnabledForUnmanagedResources() {
		return true;
	}

	protected boolean isEnabledForIgnoredResources() {
		return true;
	}

	/**
	 * Walk through all selected resources.
	 * If it is a folder or project, go recursive.
	 * @return an array of <code>IResource</code>
	 * @throws CoreException
	 */
	protected IResource[] getAllSelectedResources() throws CoreException {
		if (this.getSelection().isEmpty())
			return new IResource[0];
		Iterator it = this.getSelection().iterator();
		List resources = new ArrayList();
		while (it.hasNext()) {
			Object element = it.next();
			Object adapter = getAdapter(element, IResource.class);
			if (adapter instanceof IResource) {
				IResource resource = (IResource) adapter;
				if ((resource.getType() == IResource.PROJECT)
						|| (resource.getType() == IResource.FOLDER)) {
					resource.accept(new ResourceVisitor(resources));
				} else {
					resources.add(adapter);
				}
			}
		}
		return (IResource[]) resources.toArray(new IResource[resources.size()]);
	}

	
	/**
	 * Determines, wheather the the the two revisions are in the same branch
	 * 
	 * @param localRevision local revision number
	 * @param remoteRevision remote revision number
	 * @return wheather both are in the same branch
	 */
	private boolean isSameBranch(String localRevision, String remoteRevision) {
		int localDigits[] = Util.convertToDigits(localRevision);
		if (localDigits.length == 0)
			return false;
		int remoteDigits[] = Util.convertToDigits(remoteRevision);
		if (remoteDigits.length == 0)
			return false;

		if (remoteDigits.length > localDigits.length) {
			return false;
		} else {
			boolean retval = true;
			int i;
			for (i = 0; i < (remoteDigits.length - 1); i++) {
				if (remoteDigits[i] != localDigits[i])
					retval = false;
			}
			if (remoteDigits[i] > localDigits[i])
				retval = false;
			return retval;
		}
	}

	private RemoteLogOperation rLogOperation = null;

	private LogEntryCache lec = new RemoteLogOperation.LogEntryCache();

	/**
	 * Retrieves for a given remote resource the author and adds all found comments to him.
	 * Adds as well information about the resource.
	 * 
	 * @param resource remote resource
	 * @param authors global list of authors
	 * @throws CVSException
	 * @throws TeamException
	 */
	public void getLogEntryForResource(IResource resource, Map authors)
			throws CVSException, TeamException {
		ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
		if (cvsResource == null) {
			return;
		}
		ICVSRemoteResource remote = CVSWorkspaceRoot
				.getRemoteResourceFor(cvsResource);
		if (remote == null) {
			return;
		}
		ResourceSyncInfo syncInfo = remote.getSyncInfo();
		syncInfo.getTag();

		/** entries[cc] is the LogEntry, which is added to the ArrayListPlusProject*/ 
		ILogEntry entries[] = lec.getLogEntries(remote);
		if (entries != null) {
			for (int cc = 0; cc < entries.length; cc++) {
				if (syncInfo == null
						|| isSameBranch(syncInfo.getRevision(), entries[cc]
								.getRevision())) {entries[cc].getTags();
					Map comments = (Map) authors.get(entries[cc].getAuthor());
					if (comments == null) { // first time
						comments = new HashMap();
						authors.put(entries[cc].getAuthor(), comments);
					}
					ArrayListPlusProject list = (ArrayListPlusProject) comments
							.get(entries[cc].getComment());
					if (list == null) { //first time
						list = new ArrayListPlusProject();
						list.setProject(resource.getProject());
						comments.put(entries[cc].getComment(), list);
					}
					list.add(new LinkLogEntryWithResource(entries[cc], resource)); //another obj
				}
			}
		}
	}

	/** buildChangeLog*/
	public List buildChangeLog(Map authors) {
		
		/** Sorting List of ILogEnty elements according to their date*/
		Comparator compare = new Comparator() {
			public boolean equals(Object obj) {
				return false;
			}

			public int compare(Object o1, Object o2) {
				if (o1 instanceof LinkLogEntryWithResource && o2 instanceof LinkLogEntryWithResource) {
					LinkLogEntryWithResource l1 = (LinkLogEntryWithResource) o1;
					LinkLogEntryWithResource l2 = (LinkLogEntryWithResource) o2;
					ILogEntry rev1 = l1.getLogEntry();
					ILogEntry rev2 = l2.getLogEntry();
//					if (o1 instanceof ILogEntry && o2 instanceof ILogEntry) {
//						ILogEntry rev1 = (ILogEntry) o1;
//						ILogEntry rev2 = (ILogEntry) o2;
						return rev1.getDate().compareTo(rev2.getDate());
//					}
				}
				return 0;
			}
		};
		
		/**commited date <-> ArrayListPlusProject*/
		Map changeLog = new TreeMap(); 
		/** Iterating over all the authors in the HashMap and gets back the comment Map*/
		for (Iterator iter = authors.entrySet().iterator(); iter.hasNext();) {
			Map.Entry entry = (Map.Entry) iter.next();
			Map msgs = (Map) entry.getValue(); /** the comment map*/
			/** Iterating over all the comments in the Comment HashMap*/
			for (Iterator iterMsgs = msgs.entrySet().iterator(); iterMsgs
					.hasNext();) {
				Map.Entry msgEntry = (Map.Entry) iterMsgs.next();
				ArrayListPlusProject lst = (ArrayListPlusProject) msgEntry
						.getValue();
				Collections.sort(lst, compare);
				ILogEntry previousRev = null;
				ArrayListPlusProject tempList = new ArrayListPlusProject();
				tempList.setProject(lst.getProject());
				// seconds times milliseconds.
				long duration = 180L * 1000L;
				/** Iterating over the ArrayList+Project */
				for (int cc = 0; cc < lst.size(); cc++) {
//					ILogEntry rev = (ILogEntry) lst.get(cc);
					ILogEntry rev = ((LinkLogEntryWithResource) lst.get(cc)).getLogEntry();
					LinkLogEntryWithResource llwres = (LinkLogEntryWithResource) lst.get(cc);
					if (previousRev != null) {
						if ((rev.getDate().getTime() - previousRev.getDate()
								.getTime()) < duration) {
							// between two revisions is less than 3 min
						} else {
							tempList = new ArrayListPlusProject();
							tempList.setProject(lst.getProject());
							changeLog.put(rev.getDate(), tempList);
						}
					} else {
						changeLog.put(rev.getDate(), tempList);
					}
					tempList.add(llwres);
					previousRev = rev;
				}
			}
		}
		
		/* Now all the resources are packaged in commited updates
		 * values is a List of ArrayListPlusProject */
		
		// Add to entries array
		List values[] = (List[]) changeLog.values().toArray(new List[0]);
		List changeLogEntries = new ArrayList();
		for (int cc = values.length - 1; cc > -1; cc--) {
			ArrayListPlusProject revs = (ArrayListPlusProject) values[cc];
			CVSSearchEntry entry = null;
			/** Iterating over the ILogEntry elements*/
			for (Iterator iterRevs = revs.iterator(); iterRevs.hasNext();) {
				/* several ILogEntry for one CVSSearchEntry ?*/
//				ILogEntry rev = (ILogEntry) iterRevs.next();
				LinkLogEntryWithResource llewres = (LinkLogEntryWithResource) iterRevs.next();
				ILogEntry rev = llewres.getLogEntry();
				if (entry == null) { /** Create new CVSSearchEntry */
					String msg = rev.getComment();
					String origMsg = new String(msg);
					msg = msg.replace('\n', ' ');
					msg = msg.replace('\r', ' ');
					entry = new CVSSearchEntry(rev.getAuthor(), msg, origMsg,
							rev.getDate(), revs.getProject());
//					CVSTag[] tags = rev.getTags();
//					if (tags != null && tags.length > 0) {
//						System.out.println(tags[0].getName());
//						
//					}
//					String revision = rev.getRevision();
//					String project = revs.getProject().getName();
					changeLogEntries.add(entry);
				}
				
				entry.addResource(llewres.getResource());
				
				/**add resources instead of file paths */
				
				entry.addFilePaths(rev.getRemoteFile()
						.getRepositoryRelativePath()
						+ " " + rev.getRevision());
//				ICVSRemoteFile iRFile = rev.getRemoteFile();
				entry.addFiles(rev.getRemoteFile().getName() + " "
						+ rev.getRevision());
			}
		}
		return changeLogEntries;
	}

	public class BackgroundJob extends Job {
		SearchResultView view = null;

		/**
		 * @param name
		 */
		public BackgroundJob(String name) {
			super(name);
			view = (SearchResultView) showView("CVSSearchEngine.view");
			view.setChangeLogJob(this);
		}

		private Status displayErrorMessage(Exception ex) {
			StringWriter sWriter = new StringWriter();
			PrintWriter pWriter = new PrintWriter(sWriter);
			ex.printStackTrace(pWriter);
			return new Status(IStatus.ERROR, "CVSChangeLog", IStatus.OK,
					sWriter.toString(), ex);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
		 */
		protected IStatus run(IProgressMonitor monitor) {
			try {
				changeLogEntriesT = null;
				/** Get all the selected resources */
				IResource[] resources = SearchAction.this.getAllSelectedResources();
				List remoteResources = new ArrayList();
				
				/** Get all the remote resources */
				for (int i = 0; i < resources.length; i++) {
					ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resources[i]);
					ICVSRemoteResource remote = CVSWorkspaceRoot.getRemoteResourceFor(cvsResource);
					if (remote != null) {
						remoteResources.add(remote);
					}
				}
				
				/** create a new HashMap */
				Map authors = new HashMap();
				
				/** if the monitor was canceld, stop the opperation*/
				if (monitor.isCanceled()) {
					throw new OperationCanceledException();
				}
				
				/** create an array of the remote resources*/
				ICVSRemoteResource[] remoteArray = ((ICVSRemoteResource[]) remoteResources
						.toArray(new ICVSRemoteResource[remoteResources.size()]));
				/** Create Logoperation*/
				rLogOperation = new RemoteLogOperation(view, remoteArray, null, null, lec);
				rLogOperation.execute(monitor);// connects to cvs
				
				/** Iterate over the Resources and add fill the <code>authors</code> HashMap*/
				for (int cc = 0; cc < resources.length; cc++) {
					SearchAction.this.getLogEntryForResource(resources[cc], authors);
					if (monitor.isCanceled()) {
						throw new OperationCanceledException();
					}
				}
				
				/** Create the final result of the authors HashMap*/
				final List changeLogEntries = SearchAction.this
						.buildChangeLog(authors);
				if (monitor.isCanceled()) {
					throw new OperationCanceledException();
				}
//				Display display = view.getViewer().getControl().getDisplay();
//				if (!display.isDisposed()) {
//					display.asyncExec(new Runnable() {
//						public void run() {
//							//view.showChangeLog(changeLogEntries);
//							changeLogEntriesT = changeLogEntries;
//							//TODO store the values somewhere.
//						}
//					});
//				}
				changeLogEntriesT = changeLogEntries;
				
			} catch (CVSException ex) {
				return (displayErrorMessage(ex));
			} catch (TeamException ex) {
				return (displayErrorMessage(ex));
			} catch (CoreException ex) {
				return (displayErrorMessage(ex));
			} catch (OperationCanceledException ex) {
				return new Status(IStatus.CANCEL, "CVSChangeLog", IStatus.OK,
						"Cancelling CVS ChangeLog operation.", null);
			} catch (Exception ex) {
				return (displayErrorMessage(ex));
			}
			return new Status(IStatus.OK, "CVSChangeLog", IStatus.OK,
					"Finished", null);
		}
	}
}
